//
//	Author:	Michael Cameron
//	Email:	chronokun@hotmail.com
//
//	Modified by: scuti@teknik.io
//

#include <iostream>
#include <exception>
#include <fstream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>

#include "cxxopts.hpp"

#include "oopless-parser.hpp"
#include "brushdef.hpp"

using namespace std;

//ease of reading function pointer
typedef void (*brushdef) (std::stringstream &, 
                          const std::vector<TPlanePoints> &);

#define ARG_INPUT_SHORTALIAS "i"
#define ARG_OUTPUT_SHORTALIAS "o"
#define ARG_BRUSHFORMAT "gtk"
cxxopts::Options arguments(int ac, char ** av) {
    vector<string> parg = {"input", "output"};
    cxxopts::Options o("reflex2q3", 
                       "Converts Reflex maps to idTech 3 maps.");
    o.add_options("All")
        ("h,help", "displays help")
        ("i,input", 
            "required, input map file from Reflex", 
            cxxopts::value<string>(), "FILE")
        ("o,output", 
            "required, output map file for idTech 3 or similar map editors", 
            cxxopts::value<string>(), "FILE")
        (ARG_BRUSHFORMAT, 
            "optional, output in GTK Radiant's brush definition format")
        ("e,ent", 
            "optional, convert game entities given a table of equivalents", 
            cxxopts::value<string>(), "FILE")
        ;
    o.parse_positional(parg);
    auto options = o.parse(ac, av);
    o.show_positional_help();
    if (options.count(ARG_INPUT_SHORTALIAS) < 1 || 
        options.count(ARG_OUTPUT_SHORTALIAS) < 1) {
            cout << o.help({"All"}) << endl;
            cerr << "error: no input or output file given" << endl;
            exit(EXIT_FAILURE);
    }
    else if (options.count("help")) {
        cout << o.help() << endl;
    }

    return o;
}

stringstream loadmap(const char *filename) {
    ifstream fin;
    fin.open(filename);
    if (!fin.good()) {
        cout << "error: can not open input file." << endl;
    }
    vector<string> v;
    string line;
    stringstream output;
    while (getline(fin, line)) {
        v.push_back(line);
    }
    move(v.begin(), v.end(), ostream_iterator<string>(output, "\n"));
    return output;
}

bool convert_worldspawn(const cxxopts::ParseResult &o, 
                        vector<vector<string> > &q) {
    bool     is_ok  = false;
    brushdef fn     = &brushdef_net;
    if (o.count(ARG_BRUSHFORMAT)) {
        fn = &brushdef_gtk;
    }
    string inputfile = o[ARG_INPUT_SHORTALIAS].as<string>();
    stringstream mapdata = loadmap(inputfile.c_str());
    try {
        is_ok = convertmap(
                    mapdata,  // in file
                    o[ARG_OUTPUT_SHORTALIAS].as<string>().c_str(), // out file
                    fn,   // brush definition
                    q);   // queue of entities
    } catch (exception &e) {
        cout << e.what() << endl;
        cout << "exception encountered while converting map geometry"
                " - unrecoverable failure" << endl;
    }
    if (is_ok) {
        cout << "Successfully converted map " 
             << o[ARG_INPUT_SHORTALIAS].as<string>()
             << " to " 
             << o[ARG_OUTPUT_SHORTALIAS].as<string>()
             << endl;
    } else {
        cout << "Failed to convert map." << endl;
    }
    return is_ok;
}

void print_entities(vector<vector<string> > &q) {
     vector<string> entity;
    do {
        entity = q.front();
        cout << "--------------------------" << endl;
        for (const string &s : entity) {
            cout << s << endl;
        }
        cout << "--------------------------" << endl;
    } while (!q.empty());   
}


void convert_entities(const cxxopts::ParseResult &o, const vector<vector<string> > &q) {
    string entfile = o["e"].as<string>();
    if (entfile.empty()) {
        cout << "No entity data file given"
                "- skipping entity conversion" << endl;
        return;
    }
    EntityConverter e(entfile);
    e.extractMapInfo(q);
    ofstream fout(o[ARG_OUTPUT_SHORTALIAS].as<string>(), ios::app);
    brushdef fn = &brushdef_net;
    vector<string> entity;
    if (o.count(ARG_BRUSHFORMAT)) {
        fn = &brushdef_gtk;
    }
    for (const vector<string> &entity : q) {
        try {
            write(entity, fout, fn, e);
        } catch (exception &e) {
            cerr << e.what() << endl;
        }
    }
}

int main(int argc, char** argv)
{
  try {
    cxxopts::Options p = arguments(argc, argv);
    auto options = p.parse(argc, argv);
    vector<vector<string> > entities;
    bool is_ok = convert_worldspawn(options, entities);
//    print_entities(entities);
    convert_entities(options, entities);
  }
  catch (cxxopts::exceptions::no_such_option e) {
    cerr << e.what() << endl
         << "./reflex2q3 -h for usage information" << endl;
    return -1;
  }
  catch (std::exception &e) {
    cerr << e.what() << endl;
    return -2;
  }

	return(0);
}
